Skip to content

fix(unifi): functional provider via Cloud Connector + writable /tmp#2467

Merged
botantler-1[bot] merged 3 commits into
mainfrom
claude/unifi-cloud-connector
Jul 4, 2026
Merged

fix(unifi): functional provider via Cloud Connector + writable /tmp#2467
botantler-1[bot] merged 3 commits into
mainfrom
claude/unifi-cloud-connector

Conversation

@devantler

Copy link
Copy Markdown
Contributor

🤖 Generated by the Daily AI Assistant

Why

provider-upjet-unifi has been unable to manage the controller since the Crossplane migration merged — every managed resource is stuck SYNCED=False. Two causes: a read-only /tmp blocks the provider before any API call, and it was pointed at a cloud URL without Cloud Connector actually enabled, so it had no write path to the controller.

What

  • Writable /tmp on the provider's DeploymentRuntimeConfig — Kyverno forces the pod read-only, but the Upjet provider needs /tmp for its Terraform workspace. (Same hunk as fix(security): harden containers with read-only root filesystems (Kubescape C-0017) #2455 — see note below.)
  • UniFi Cloud Connector — the provider now authenticates with a Cloud API key and routes writes through https://api.ui.com, which proxies to your controller. No WireGuard tunnel or public controller endpoint required.

Action needed (one-time)

Overwrite api_key at secret/infrastructure/unifi/controller in OpenBao with a write-scoped UniFi Site Manager API key (unifi.ui.com → API, Site + Application scope). The managed resources then reconcile.

Unblocks declarative management of the controller from Hetzner. The WireGuard "VPN in front of admin UIs" work is separate and unaffected. Overlaps #2455 on the one DRC file (identical change → auto-resolves, or #2455 drops it).

provider-upjet-unifi has been unable to manage the controller since the
migration merged — every managed resource is SYNCED=False. Two root causes,
both fixed here:

1. Read-only /tmp: Kyverno forces the provider pod read-only with no /tmp
   mount, but the Upjet provider writes Terraform workspaces to /tmp/<uuid>
   ("mkdir /tmp/<uuid>: read-only file system"), so nothing reconciles. Add a
   writable /tmp emptyDir + explicit hardened securityContext to the
   DeploymentRuntimeConfig. (Mirrors the identical hunk in #2455; whichever
   merges first, the other auto-resolves or drops this one file.)

2. No write path: it was pointed at a cloud URL without Cloud Connector
   enabled. Switch to UniFi Cloud Connector mode (cloud_connector: "true") so
   the provider authenticates with a Site Manager / Cloud API key and routes
   writes through https://api.ui.com, which proxies to the controller — the
   controller need NOT be reachable from Hetzner (no tunnel, no public
   endpoint). Drop the unused api_url; omit hardware_id (defaults to the first
   owner=true console).

One-time user gate: overwrite api_key at secret/infrastructure/unifi/controller
in OpenBao with a WRITE-scoped UniFi Site Manager API key.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

This PR updates UniFi credential seeding and templating to use UniFi Cloud Connector authentication with a placeholder api_key, and removes api_url from the generated credentials data. It also hardens the UniFi provider runtime configuration by adding a restrictive container securityContext and mounting a writable /tmp emptyDir volume.

Sequence Diagram(s)

sequenceDiagram
  participant VaultJob as vault-config job
  participant Vault as Vault
  participant ExternalSecret as external-secret.yaml
  participant CrossplaneProvider as UniFi Crossplane Provider

  VaultJob->>Vault: seed placeholder api_key if absent
  ExternalSecret->>Vault: fetch api_key
  ExternalSecret->>ExternalSecret: template credentials with cloud_connector "true", site "default"
  ExternalSecret->>CrossplaneProvider: provide credentials secret
Loading

Estimated code review effort: Medium

Related issues: None found.

Related PRs: None found.

Suggested labels: kubernetes, security, infrastructure

Suggested reviewers: devantler

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title matches the core change: enabling UniFi Cloud Connector support and adding a writable /tmp for the provider.
Description check ✅ Passed The description is directly about fixing provider-upjet-unifi with writable /tmp and Cloud Connector authentication.

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@k8s/providers/hetzner/infrastructure/crossplane/deployment-runtime-config-upjet-unifi.yaml`:
- Around line 47-49: The tmp emptyDir volume is currently unbounded, which can
let Terraform workspace data consume excessive node ephemeral storage. Update
the volume definition in the deployment runtime config to set a reasonable
sizeLimit on the tmp emptyDir, keeping the blast radius small while preserving
the existing volume name and mount usage.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 5774689b-1aaf-4cc6-9bee-dabf94b5d7ae

📥 Commits

Reviewing files that changed from the base of the PR and between d27ccf6 and b5c50c2.

📒 Files selected for processing (3)
  • k8s/bases/infrastructure/vault-config/job.yaml
  • k8s/providers/hetzner/apps/unifi/external-secret.yaml
  • k8s/providers/hetzner/infrastructure/crossplane/deployment-runtime-config-upjet-unifi.yaml
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • devantler-tech/actions (auto-detected)
  • devantler-tech/aws (auto-detected)
  • devantler-tech/reusable-workflows (auto-detected)
  • devantler-tech/ksail (auto-detected)
  • devantler-tech/ascoachingogvaner (auto-detected)
  • devantler-tech/wedding-app (auto-detected)
  • devantler-tech/agent-skills (auto-detected)
📜 Review details
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{yaml,yml}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{yaml,yml}: Use Kustomize overlays rather than editing base resources directly; k8s/bases/ is immutable from overlays and changes should be made with patches: in provider or cluster overlays.
Keep manifest changes small and use YAML/schema validation before submitting a manifest PR; for files with cluster context, prefer ksail workload validate / kubectl kustomize / kubectl apply --dry-run=client as appropriate.

Files:

  • k8s/providers/hetzner/infrastructure/crossplane/deployment-runtime-config-upjet-unifi.yaml
  • k8s/bases/infrastructure/vault-config/job.yaml
  • k8s/providers/hetzner/apps/unifi/external-secret.yaml
k8s/**

📄 CodeRabbit inference engine (AGENTS.md)

k8s/**: Respect Flux dependency order: bootstrapinfrastructure-controllersinfrastructureapps, with the prod-only infrastructure-overprovisioning layer hanging off infrastructure without gating apps.
Follow the hierarchical Kustomization flow: base configurations in k8s/bases/ feed provider overlays in k8s/providers/, which feed cluster overlays in k8s/clusters/.

Files:

  • k8s/providers/hetzner/infrastructure/crossplane/deployment-runtime-config-upjet-unifi.yaml
  • k8s/bases/infrastructure/vault-config/job.yaml
  • k8s/providers/hetzner/apps/unifi/external-secret.yaml
k8s/bases/infrastructure/**

📄 CodeRabbit inference engine (AGENTS.md)

k8s/bases/infrastructure/**: Under k8s/bases/infrastructure/, organize resources component-folder-first: a component's HelmRelease/HelmRepository and its own CRs should live together in a folder named after the component unless a split is required.
Split a custom resource into its own plural-Kind folder only when it cannot live with its component, such as for CRD dependency ordering or because it is cluster-scoped/cross-cutting.

Files:

  • k8s/bases/infrastructure/vault-config/job.yaml
k8s/bases/infrastructure/**/*.{yaml,yml}

📄 CodeRabbit inference engine (AGENTS.md)

k8s/bases/infrastructure/**/*.{yaml,yml}: For component-folder files, name manifests after the resource Kind in kebab-case; if a folder contains multiple resources of the same Kind, qualify filenames with a purpose suffix.
For CR-folder files, omit the folder-implied Kind from the filename and use the verb-purpose.yaml form.
Name Flux Kustomization resources flux-kustomization*.yaml; keep the kustomize build file named exactly kustomization.yaml.

Files:

  • k8s/bases/infrastructure/vault-config/job.yaml
🧠 Learnings (2)
📚 Learning: 2026-07-01T21:13:36.950Z
Learnt from: devantler
Repo: devantler-tech/platform PR: 2359
File: k8s/bases/apps/actual-budget/helm-release.yaml:62-111
Timestamp: 2026-07-01T21:13:36.950Z
Learning: When reviewing Kustomize/Helm YAML in this repo, keep the base vs provider overlay split: `k8s/bases/apps/**` and `k8s/bases/infrastructure/**` should contain each app’s full, environment-agnostic configuration (including base-level postRenderer Kustomize patches such as deployment strategy, topology spread, probes, and env injection). `k8s/providers/{docker,hetzner}/**` should only add small provider-specific deltas (e.g., `interval`, `persistence.size`) via patch files (like `k8s/providers/<provider>/apps/<app>/patches/helm-release-patch.yaml`). If configuration is identical across providers (e.g., OIDC/OAuth env vars where `${domain}` is resolved per cluster via envsubst), it belongs in the base and must not be duplicated into provider overlays.

Applied to files:

  • k8s/providers/hetzner/infrastructure/crossplane/deployment-runtime-config-upjet-unifi.yaml
  • k8s/bases/infrastructure/vault-config/job.yaml
  • k8s/providers/hetzner/apps/unifi/external-secret.yaml
📚 Learning: 2026-07-02T06:32:09.574Z
Learnt from: devantler
Repo: devantler-tech/platform PR: 2377
File: k8s/bases/infrastructure/vault-config/job.yaml:785-793
Timestamp: 2026-07-02T06:32:09.574Z
Learning: When reviewing OpenBao/Vault `vault-config` Kubernetes YAMLs, treat the infra readonly KV v2 policy pattern `secret/data/infrastructure/<area>/*` (area-wildcard) as an intentional design. Do NOT suggest narrowing these wildcard paths to specific keys as a first response.

Instead, verify the intended blast-radius scoping: each isolation unit `<area>` must have its own `infra-<area>-readonly` Vault policy, and that policy must be bound only to that area's dedicated Kubernetes auth role/ServiceAccount (e.g., `github-config`, `unifi`, `aws`). This should ensure the wildcard’s scope is controlled by the auth role binding rather than by narrowing KV paths.

Also confirm that adding new sibling secrets later under the same `<area>` path should be readable by the existing role without requiring policy edits (i.e., the wildcard path covers the new keys).

Applied to files:

  • k8s/bases/infrastructure/vault-config/job.yaml
🪛 Trivy (0.69.3)
k8s/providers/hetzner/infrastructure/crossplane/deployment-runtime-config-upjet-unifi.yaml

[info] 9-49: limit range usage

A LimitRange policy with a default requests and limits for each container should be configured

Rule: KSV-0039

Learn more

(IaC/Kubernetes)

🔇 Additional comments (4)
k8s/bases/infrastructure/vault-config/job.yaml (1)

522-543: LGTM!

k8s/providers/hetzner/apps/unifi/external-secret.yaml (2)

2-26: LGTM!


44-49: LGTM!

k8s/providers/hetzner/infrastructure/crossplane/deployment-runtime-config-upjet-unifi.yaml (1)

27-49: 🩺 Stability & Availability

No additional writable mount is needed. The /tmp emptyDir already covers this provider runtime’s write path.

			> Likely an incorrect or invalid review comment.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@devantler devantler marked this pull request as ready for review July 4, 2026 19:57
@botantler-1 botantler-1 Bot enabled auto-merge July 4, 2026 20:40
@botantler-1 botantler-1 Bot added this pull request to the merge queue Jul 4, 2026
Merged via the queue into main with commit df148cf Jul 4, 2026
15 checks passed
@botantler-1 botantler-1 Bot deleted the claude/unifi-cloud-connector branch July 4, 2026 20:44
@github-project-automation github-project-automation Bot moved this from 🏃🏻‍♂️ In Progress to ✅ Done in 🌊 Project Board Jul 4, 2026
@botantler-1

botantler-1 Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 1.98.4 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

@botantler-1 botantler-1 Bot added the released label Jul 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Status: ✅ Done

Development

Successfully merging this pull request may close these issues.

1 participant